/**********************************************************************

  Audacity: A Digital Audio Editor

  Sequence.h

  Dominic Mazzoni

**********************************************************************/

#ifndef __AUDACITY_SEQUENCE__
#define __AUDACITY_SEQUENCE__

#include <atomic>
#include <deque>
#include <functional>

#include "SampleFormat.h"
#include "XMLTagHandler.h"

#include "SampleCount.h"
#include "AudioSegmentSampleView.h"

class SampleBlock;
class SampleBlockFactory;
using SampleBlockFactoryPtr = std::shared_ptr<SampleBlockFactory>;

// This is an internal data structure!  For advanced use only.
class SeqBlock
{
public:
    using SampleBlockPtr = std::shared_ptr<SampleBlock>;
    SampleBlockPtr sb;
    ///the sample in the global wavetrack that this block starts at.
    sampleCount start;

    SeqBlock()
        : sb{}, start(0)
    {}

    SeqBlock(const SampleBlockPtr& sb_, sampleCount start_)
        : sb(sb_), start(start_)
    {}

    // Construct a SeqBlock with changed start, same file
    SeqBlock Plus(sampleCount delta) const
    {
        return SeqBlock(sb, start + delta);
    }
};
class BlockArray : public std::deque<SeqBlock>
{
};
using BlockPtrArray = std::deque<SeqBlock*>; // non-owning pointers

class WAVE_TRACK_API Sequence final : public XMLTagHandler
{
public:

    static const char* Sequence_tag;
    static const char* WaveBlock_tag;

    //
    // Static methods
    //

    static void SetMaxDiskBlockSize(size_t bytes);
    static size_t GetMaxDiskBlockSize();

    //! true if nValue is one of the sampleFormat enum values
    static bool IsValidSampleFormat(const int nValue);

    //
    // Constructor / Destructor / Duplicator
    //

    Sequence(const SampleBlockFactoryPtr& pFactory, SampleFormats formats);

    //! Does not copy un-flushed append buffer data
    Sequence(const Sequence& orig, const SampleBlockFactoryPtr& pFactory);

    Sequence(const Sequence&) = delete;
    Sequence& operator=(const Sequence&) = delete;

    ~Sequence();

    //
    // Editing
    //

    sampleCount GetNumSamples() const { return mNumSamples; }

    //! Get a range of samples from the sequence
    /*
     If the requested range is not all defined, the buffer may still contain
     valid results, with zero filling

     @param start offset into sequence
     @param len number of samples to put in buffer
     @param mayThrow if true, propagate read error exceptions
     @return whether [start, start + len) was within the defined range of the
     sequence and there were no read errors
     */
    bool Get(samplePtr buffer, sampleFormat format, sampleCount start, size_t len, bool mayThrow) const;

    /*!
     Get a view of the lesser of `len` samples or what remains after `start`
     @pre `start < GetNumSamples()`
     */
    AudioSegmentSampleView
    GetFloatSampleView(sampleCount start, size_t len, bool mayThrow) const;

    //! Pass nullptr to set silence
    /*! Note that len is not size_t, because nullptr may be passed for buffer, in
       which case, silence is inserted, possibly a large amount. */
    /*! @excsafety{Strong} */
    void SetSamples(constSamplePtr buffer, sampleFormat format, sampleCount start, sampleCount len, sampleFormat effectiveFormat /*!<
         Make the effective format of the data at least the minumum of this
         value and `format`.  (Maybe wider, if merging with preexistent data.)
         If the data are later narrowed from stored format, but not narrower
         than the effective, then no dithering will occur.
      */
                    );

    // Return non-null, or else throw!
    // Must pass in the correct factory for the result.  If it's not the same
    // as in this, then block contents must be copied.
    std::unique_ptr<Sequence> Copy(const SampleBlockFactoryPtr& pFactory, sampleCount s0, sampleCount s1) const;
    /*! @excsafety{Strong} */
    void Paste(sampleCount s0, const Sequence* src);

    size_t GetIdealAppendLen() const;

    /*!
        Samples may be retained in a memory buffer, pending Flush()
        If there are exceptions, an unspecified prefix of buffer may be
        appended

        @return true if at least one sample block was added
        @excsafety{Weak}
     */
    bool Append(
        constSamplePtr buffer, sampleFormat format, size_t len, size_t stride, sampleFormat effectiveFormat /*!<
         Make the effective format of the data at least the minumum of this
         value and `format`.  (Maybe wider, if merging with preexistent data.)
         If the data are later narrowed from stored format, but not narrower
         than the effective, then no dithering will occur.
      */
        );

    /*! @excsafety{Mixed} */
    /*! @excsafety{No-fail} -- The Sequence will be in a flushed state. */
    /*! @excsafety{Partial}
    -- Some initial portion (maybe none) of the append buffer of the
     Sequence gets appended; no previously flushed contents are lost. */
    void Flush();

    //! Append data, not coalescing blocks, returning a pointer to the new block.
    //! No dithering applied.
    /*! @excsafety{Strong} */
    SeqBlock::SampleBlockPtr AppendNewBlock(
        constSamplePtr buffer, sampleFormat format, size_t len);
    //! Append a complete block, not coalescing
    /*! @excsafety{Strong} */
    void AppendSharedBlock(const SeqBlock::SampleBlockPtr& pBlock);
    /*! @excsafety{Strong} */
    void Delete(sampleCount start, sampleCount len);

    /*! @excsafety{Strong} */
    void SetSilence(sampleCount s0, sampleCount len);
    /*! @excsafety{Strong} */
    void InsertSilence(sampleCount s0, sampleCount len);

    const SampleBlockFactoryPtr& GetFactory() const
    {
        return mpFactory;
    }

    //
    // XMLTagHandler callback methods for loading and saving
    //

    bool HandleXMLTag(const std::string_view& tag, const AttributesList& attrs) override;
    void HandleXMLEndTag(const std::string_view& tag) override;
    XMLTagHandler* HandleXMLChild(const std::string_view& tag) override;
    void WriteXML(XMLWriter& xmlFile) const /* not override */;

    bool GetErrorOpening() const { return mErrorOpening; }

    //
    // Lock all of this sequence's sample blocks, keeping them
    // from being destroyed when closing.

    //! Should be called upon project close.  Not balanced by unlocking calls.
    /*! @excsafety{No-fail} */
    bool CloseLock() noexcept;

    //
    // Manipulating Sample Format
    //

    SampleFormats GetSampleFormats() const;

    //! @return whether there was a change of format
    /*! @excsafety{Strong} */
    bool ConvertToSampleFormat(sampleFormat format, const std::function<void(size_t)>& progressReport = {});

    //
    // Retrieving summary info
    //

    std::pair<float, float> GetMinMax(
        sampleCount start, sampleCount len, bool mayThrow) const;
    float GetRMS(sampleCount start, sampleCount len, bool mayThrow) const;

    //
    // Getting block size and alignment information
    //

    // These return a nonnegative number of samples meant to size a memory buffer
    size_t GetBestBlockSize(sampleCount start) const;
    size_t GetMaxBlockSize() const;
    size_t GetIdealBlockSize() const;

    //
    // This should only be used if you really, really know what
    // you're doing!
    //
    const BlockArray& GetBlockArray() const { return mBlock; }

    size_t GetAppendBufferLen() const { return mAppendBufferLen; }
    constSamplePtr GetAppendBuffer() const { return mAppendBuffer.ptr(); }

private:

    //
    // Private static variables
    //

    static size_t sMaxDiskBlockSize;

    //
    // Private variables
    //

    SampleBlockFactoryPtr mpFactory;
    std::atomic<size_t> mBlockCount{ 0 };
    BlockArray mBlock;
    SampleFormats mSampleFormats;

    // Not size_t!  May need to be large:
    sampleCount mNumSamples{ 0 };

    size_t mMinSamples;  // min samples per block
    size_t mMaxSamples;  // max samples per block

    SampleBuffer mAppendBuffer {};
    size_t mAppendBufferLen { 0 };
    sampleFormat mAppendEffectiveFormat{ narrowestSampleFormat };

    bool mErrorOpening{ false };

    //
    // Private methods
    //

    //! @return possibly a large or negative value
    sampleCount GetBlockStart(sampleCount position) const;

    //! Does not do any dithering
    /*! @excsafety{Strong} */
    SeqBlock::SampleBlockPtr DoAppend(
        constSamplePtr buffer, sampleFormat format, size_t len, bool coalesce);

    static void AppendBlock(SampleBlockFactory* pFactory, sampleFormat format, BlockArray& blocks, sampleCount& numSamples,
                            const SeqBlock& b);

    // Accumulate NEW block files onto the end of a block array.
    // Does not change this sequence.  The intent is to use
    // CommitChangesIfConsistent later.
    static void Blockify(SampleBlockFactory& factory, size_t maxSamples, sampleFormat format, BlockArray& list, sampleCount start,
                         constSamplePtr buffer, size_t len);

    bool Get(int b, samplePtr buffer, sampleFormat format, sampleCount start, size_t len, bool mayThrow) const;

public:

    //
    // Public methods
    //

    int FindBlock(sampleCount pos) const;

    static bool Read(samplePtr buffer, sampleFormat format, const SeqBlock& b, size_t blockRelativeStart, size_t len, bool mayThrow);

    // This function throws if the track is messed up
    // because of inconsistent block starts & lengths
    void ConsistencyCheck(const wxChar* whereStr, bool mayThrow = true) const;

    // This function prints information to stdout about the blocks in the
    // tracks and indicates if there are inconsistencies.
    static void DebugPrintf(const BlockArray& block, sampleCount numSamples, wxString* dest);

private:
    static void ConsistencyCheck(const BlockArray& block, size_t maxSamples, size_t from, sampleCount numSamples, const wxChar* whereStr,
                                 bool mayThrow = true);

    // The next two are used in methods that give a strong guarantee.
    // They either throw because final consistency check fails, or swap the
    // changed contents into place.

    void CommitChangesIfConsistent(BlockArray& newBlock, sampleCount numSamples, const wxChar* whereStr);

    void AppendBlocksIfConsistent(BlockArray& additionalBlocks, bool replaceLast, sampleCount numSamples, const wxChar* whereStr);
};

#endif // __AUDACITY_SEQUENCE__
